#ifndef PFS_H_
#define PFS_H_

#include <ctype.h>
#include <dirent.h>
#include <fcntl.h>
#include <fuse.h>
#include <libgen.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/xattr.h>

/** Get file attributes */
int wb_getattr(const char *path, struct stat *statbuf)
{
    int retstat = 0;
    char fpath[PATH_MAX];

    log_msg("\nwb_getattr(path=\"%s\", statbuf=0x%08x)\n",
      path, statbuf);
    wb_fullpath(fpath, path);

    retstat = lstat(fpath, statbuf);
    if (retstat != 0)
    retstat = wb_error("wb_getattr lstat");

    log_stat(statbuf);

    return retstat;
}

/** Create a directory */
int wb_mkdir(const char *path, mode_t mode)
{
    int retstat = 0;
    char fpath[PATH_MAX];

    log_msg("\nwb_mkdir(path=\"%s\", mode=0%3o)\n",
        path, mode);
    wb_fullpath(fpath, path);

    retstat = mkdir(fpath, mode);
    if (retstat < 0)
    retstat = wb_error("wb_mkdir mkdir");

    return retstat;
}

/** Remove a file */
int wb_unlink(const char *path)
{
    int retstat = 0;
    char fpath[PATH_MAX];

    log_msg("wb_unlink(path=\"%s\")\n",
        path);
    wb_fullpath(fpath, path);

    retstat = unlink(fpath);
    if (retstat < 0)
    retstat = wb_error("wb_unlink unlink");

    return retstat;
}

/** Remove a directory */
int wb_rmdir(const char *path)
{
    int retstat = 0;
    char fpath[PATH_MAX];

    log_msg("wb_rmdir(path=\"%s\")\n",
        path);
    wb_fullpath(fpath, path);

    retstat = rmdir(fpath);
    if (retstat < 0)
    retstat = wb_error("wb_rmdir rmdir");

    return retstat;
}

/** Rename a file */
// both path and newpath are fs-relative
int wb_rename(const char *path, const char *newpath)
{
    int retstat = 0;
    char fpath[PATH_MAX];
    char fnewpath[PATH_MAX];

    log_msg("\nwb_rename(fpath=\"%s\", newpath=\"%s\")\n",
        path, newpath);
    wb_fullpath(fpath, path);
    wb_fullpath(fnewpath, newpath);

    retstat = rename(fpath, fnewpath);
    if (retstat < 0)
    retstat = wb_error("wb_rename rename");

    return retstat;
}


/** Change the size of a file */
int wb_truncate(const char *path, off_t newsize)
{
    int retstat = 0;
    char fpath[PATH_MAX];

    log_msg("\nwb_truncate(path=\"%s\", newsize=%lld)\n",
        path, newsize);
    wb_fullpath(fpath, path);

    retstat = truncate(fpath, newsize);
    if (retstat < 0)
    wb_error("wb_truncate truncate");

    return retstat;
}

/** File open operation
 *
 * No creation, or truncation flags (O_CREAT, O_EXCL, O_TRUNC)
 * will be passed to open().  Open should check if the operation
 * is permitted for the given flags.  Optionally open may also
 * return an arbitrary filehandle in the fuse_file_info structure,
 * which will be passed to all file operations.
 */
int wb_open(const char *path, struct fuse_file_info *fi)
{
    int retstat = 0;
    int fd;
    char fpath[PATH_MAX];

    log_msg("\nwb_open(path\"%s\", fi=0x%08x)\n",
        path, fi);
    wb_fullpath(fpath, path);

    fd = open(fpath, fi->flags);
    if (fd < 0)
    retstat = wb_error("wb_open open");

    fi->fh = fd;
    log_fi(fi);

    return retstat;
}

/** Read data from an open file
 *
 * Read should return exactly the number of bytes requested except
 * on EOF or error, otherwise the rest of the data will be
 * substituted with zeroes.  An exception to this is when the
 * 'direct_io' mount option is specified, in which case the return
 * value of the read system call will reflect the return value of
 * this operation.
 *
 */
// I don't fully understand the documentation above -- it doesn't
// match the documentation for the read() system call which says it
// can return with anything up to the amount of data requested. nor
// with the fusexmp code which returns the amount of data also
// returned by read.
int wb_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
    int retstat = 0;

    log_msg("\nwb_read(path=\"%s\", buf=0x%08x, size=%d, offset=%lld, fi=0x%08x)\n",
        path, buf, size, offset, fi);
    // no need to get fpath on this one, since I work from fi->fh not the path
    log_fi(fi);

    retstat = pread(fi->fh, buf, size, offset);
    if (retstat < 0)
    retstat = wb_error("wb_read read");

    return retstat;
}

/** Write data to an open file
 *
 * Write should return exactly the number of bytes requested
 * except on error.  An exception to this is when the 'direct_io'
 * mount option is specified (see read operation).
 *
 */
// As  with read(), the documentation above is inconsistent with the
// documentation for the write() system call.
int wb_write(const char *path, const char *buf, size_t size, off_t offset,
         struct fuse_file_info *fi)
{
    int retstat = 0;

    log_msg("\nwb_write(path=\"%s\", buf=0x%08x, size=%d, offset=%lld, fi=0x%08x)\n",
        path, buf, size, offset, fi
        );
    // no need to get fpath on this one, since I work from fi->fh not the path
    log_fi(fi);

    retstat = pwrite(fi->fh, buf, size, offset);
    if (retstat < 0)
    retstat = wb_error("wb_write pwrite");

    return retstat;
}

/** Possibly flush cached data
 *
 * BIG NOTE: This is not equivalent to fsync().  It's not a
 * request to sync dirty data.
 *
 * Flush is called on each close() of a file descriptor.  So if a
 * filesystem wants to return write errors in close() and the file
 * has cached dirty data, this is a good place to write back data
 * and return any errors.  Since many applications ignore close()
 * errors this is not always useful.
 *
 * NOTE: The flush() method may be called more than once for each
 * open().  This happens if more than one file descriptor refers
 * to an opened file due to dup(), dup2() or fork() calls.  It is
 * not possible to determine if a flush is final, so each flush
 * should be treated equally.  Multiple write-flush sequences are
 * relatively rare, so this shouldn't be a problem.
 *
 * Filesystems shouldn't assume that flush will always be called
 * after some writes, or that if will be called at all.
 *
 * Changed in version 2.2
 */
int wb_flush(const char *path, struct fuse_file_info *fi)
{
    int retstat = 0;

    log_msg("\nwb_flush(path=\"%s\", fi=0x%08x)\n", path, fi);
    // no need to get fpath on this one, since I work from fi->fh not the path
    log_fi(fi);

    return retstat;
}

/** Release an open file
 *
 * Release is called when there are no more references to an open
 * file: all file descriptors are closed and all memory mappings
 * are unmapped.
 *
 * For every open() call there will be exactly one release() call
 * with the same flags and file descriptor.  It is possible to
 * have a file opened more than once, in which case only the last
 * release will mean, that no more reads/writes will happen on the
 * file.  The return value of release is ignored.
 *
 * Changed in version 2.2
 */
int wb_release(const char *path, struct fuse_file_info *fi)
{
    int retstat = 0;

    log_msg("\nwb_release(path=\"%s\", fi=0x%08x)\n",
      path, fi);
    log_fi(fi);

    // We need to close the file.  Had we allocated any resources
    // (buffers etc) we'd need to free them here as well.
    retstat = close(fi->fh);

    return retstat;
}

/** Read directory
 *
 * This supersedes the old getdir() interface.  New applications
 * should use this.
 *
 * The filesystem may choose between two modes of operation:
 *
 * 1) The readdir implementation ignores the offset parameter, and
 * passes zero to the filler function's offset.  The filler
 * function will not return '1' (unless an error happens), so the
 * whole directory is read in a single readdir operation.  This
 * works just like the old getdir() method.
 *
 * 2) The readdir implementation keeps track of the offsets of the
 * directory entries.  It uses the offset parameter and always
 * passes non-zero offset to the filler function.  When the buffer
 * is full (or an error happens) the filler function will return
 * '1'.
 *
 * Introduced in version 2.3
 */
int wb_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
           struct fuse_file_info *fi)
{
    int retstat = 0;
    DIR *dp;
    struct dirent *de;

    log_msg("\nwb_readdir(path=\"%s\", buf=0x%08x, filler=0x%08x, offset=%lld, fi=0x%08x)\n",
        path, buf, filler, offset, fi);
    // once again, no need for fullpath -- but note that I need to cast fi->fh
    dp = (DIR *) (uintptr_t) fi->fh;

    // Every directory contains at least two entries: . and ..  If my
    // first call to the system readdir() returns NULL I've got an
    // error; near as I can tell, that's the only condition under
    // which I can get an error from readdir()
    de = readdir(dp);
    if (de == 0) {
    retstat = wb_error("wb_readdir readdir");
    return retstat;
    }

    // This will copy the entire directory into the buffer.  The loop exits
    // when either the system readdir() returns NULL, or filler()
    // returns something non-zero.  The first case just means I've
    // read the whole directory; the second means the buffer is full.
    do {
    log_msg("calling filler with name %s\n", de->d_name);
    if (filler(buf, de->d_name, NULL, 0) != 0) {
        log_msg("    ERROR wb_readdir filler:  buffer full");
        return -ENOMEM;
    }
    } while ((de = readdir(dp)) != NULL);

    log_fi(fi);

    return retstat;
}

/**
 * Initialize filesystem
 *
 * The return value will passed in the private_data field of
 * fuse_context to all file operations and as a parameter to the
 * destroy() method.
 *
 * Introduced in version 2.3
 * Changed in version 2.6
 */
// Undocumented but extraordinarily useful fact:  the fuse_context is
// set up before this function is called, and
// fuse_get_context()->private_data returns the user_data passed to
// fuse_main().  Really seems like either it should be a third
// parameter coming in here, or else the fact should be documented
// (and this might as well return void, as it did in older versions of
// FUSE).
void *wb_init(struct fuse_conn_info *conn)
{

    log_msg("\nwb_init()\n");

    return wb_DATA;
}

/**
 * Create and open a file
 *
 * If the file does not exist, first create it with the specified
 * mode, and then open it.
 *
 * If this method is not implemented or under Linux kernel
 * versions earlier than 2.6.15, the mknod() and open() methods
 * will be called instead.
 *
 * Introduced in version 2.5
 */
int wb_create(const char *path, mode_t mode, struct fuse_file_info *fi)
{
    int retstat = 0;
    char fpath[PATH_MAX];
    int fd;

    log_msg("\nwb_create(path=\"%s\", mode=0%03o, fi=0x%08x)\n",
        path, mode, fi);
    wb_fullpath(fpath, path);

    fd = creat(fpath, mode);
    if (fd < 0)
    retstat = wb_error("wb_create creat");

    fi->fh = fd;

    log_fi(fi);

    return retstat;
}

/**
 * Change the size of an open file
 *
 * This method is called instead of the truncate() method if the
 * truncation was invoked from an ftruncate() system call.
 *
 * If this method is not implemented or under Linux kernel
 * versions earlier than 2.6.15, the truncate() method will be
 * called instead.
 *
 * Introduced in version 2.5
 */
int wb_ftruncate(const char *path, off_t offset, struct fuse_file_info *fi)
{
    int retstat = 0;

    log_msg("\nwb_ftruncate(path=\"%s\", offset=%lld, fi=0x%08x)\n",
        path, offset, fi);
    log_fi(fi);

    retstat = ftruncate(fi->fh, offset);
    if (retstat < 0)
    retstat = wb_error("wb_ftruncate ftruncate");

    return retstat;
}

/**
 * Get attributes from an open file
 *
 * This method is called instead of the getattr() method if the
 * file information is available.
 *
 * Currently this is only called after the create() method if that
 * is implemented (see above).  Later it may be called for
 * invocations of fstat() too.
 *
 * Introduced in version 2.5
 */
// Since it's currently only called after wb_create(), and wb_create()
// opens the file, I ought to be able to just use the fd and ignore
// the path...
int wb_fgetattr(const char *path, struct stat *statbuf, struct fuse_file_info *fi)
{
    int retstat = 0;

    log_msg("\nwb_fgetattr(path=\"%s\", statbuf=0x%08x, fi=0x%08x)\n",
        path, statbuf, fi);
    log_fi(fi);

    retstat = fstat(fi->fh, statbuf);
    if (retstat < 0)
    retstat = wb_error("wb_fgetattr fstat");

    log_stat(statbuf);

    return retstat;
}

struct fuse_operations wb_oper = {
  .getattr = wb_getattr,
  .readlink = wb_readlink,
  // no .getdir -- that's deprecated
  .getdir = NULL,
  .mknod = wb_mknod,
  .mkdir = wb_mkdir,
  .unlink = wb_unlink,
  .rmdir = wb_rmdir,
  .symlink = wb_symlink,
  .rename = wb_rename,
  .link = wb_link,
  .chmod = wb_chmod,
  .chown = wb_chown,
  .truncate = wb_truncate,
  .utime = wb_utime,
  .open = wb_open,
  .read = wb_read,
  .write = wb_write,
  /** Just a placeholder, don't set */ // huh???
  .statfs = wb_statfs,
  .flush = wb_flush,
  .release = wb_release,
  .fsync = wb_fsync,
  .setxattr = wb_setxattr,
  .getxattr = wb_getxattr,
  .listxattr = wb_listxattr,
  .removexattr = wb_removexattr,
  .opendir = wb_opendir,
  .readdir = wb_readdir,
  .releasedir = wb_releasedir,
  .fsyncdir = wb_fsyncdir,
  .init = wb_init,
  .destroy = wb_destroy,
  .access = wb_access,
  .create = wb_create,
  .ftruncate = wb_ftruncate,
  .fgetattr = wb_fgetattr
};

int main(int argc, char *argv[])
{
    int i;
    int fuse_stat;
    struct wb_state *wb_data;

    // wbfs doesn't do any access checking on its own (the comment
    // blocks in fuse.h mention some of the functions that need
    // accesses checked -- but note there are other functions, like
    // chown(), that also need checking!).  Since running wbfs as root
    // will therefore open Metrodome-sized holes in the system
    // security, we'll check if root is trying to mount the filesystem
    // and refuse if it is.  The somewhat smaller hole of an ordinary
    // user doing it with the allow_other flag is still there because
    // I don't want to parse the options string.
    if ((getuid() == 0) || (geteuid() == 0)) {
    fprintf(stderr, "Running wbFS as root opens unnacceptable security holes\n");
    return 1;
    }

    wb_data = calloc(sizeof(struct wb_state), 1);
    if (wb_data == NULL) {
    perror("main calloc");
    abort();
    }

    wb_data->logfile = log_open();

    // libfuse is able to do most of the command line parsing; all I
    // need to do is to extract the rootdir; this will be the first
    // non-option passed in.  I'm using the GNU non-standard extension
    // and having realpath malloc the space for the path
    // the string.
    for (i = 1; (i < argc) && (argv[i][0] == '-'); i++)
    if (argv[i][1] == 'o') i++; // -o takes a parameter; need to
                    // skip it too.  This doesn't
                    // handle "squashed" parameters

    if ((argc - i) != 2) wb_usage();

    wb_data->rootdir = realpath(argv[i], NULL);

    argv[i] = argv[i+1];
    argc--;

    fprintf(stderr, "about to call fuse_main\n");
    fuse_stat = fuse_main(argc, argv, &wb_oper, wb_data);
    fprintf(stderr, "fuse_main returned %d\n", fuse_stat);

    return fuse_stat;
}

#endif /*PFS_H_*/
